#define _WIN32_WINNT        0x0400  // kharmon.  08-01-06.  Needed for ReadDirectoryChangesW

#include <windows.h>
#include <winsock.h>
#include <memory.h>
#include <io.h>
#include <malloc.h>
#include <stdio.h>

#include "lib.h"

#include "client.h"
#include "rcvpkgupdate.h"
#include "pkgdesc.h"
#include "pkglist.h"
#include "patchsrv.h"
#include "zipfile.h"
#include "pathname.h"
#include "dir.h"
#include "log.h"
#include "opts.h"
#include "bfish.h"

RecievePackageUpdate::RecievePackageUpdate(Client* client)
	: Protocol(client)
{
	m_stream = 0;
	m_isRemoteHostValid = false;
}

RecievePackageUpdate::~RecievePackageUpdate()
{
}

bool8
RecievePackageUpdate::recognize(uint8* Msg, uint32 Len)
{
	bool8 success;

	m_multibyte = false;
	success = (bool8)(Msg[0] == 'Y' && Len > 1);
	if (!success)
	{
		success = (bool8)(((*(uint16*)&Msg[0]) == *(uint16*)"MY") && Len > 2);
		if (success)
			m_multibyte = true;
	}
	return success;
}

int32
RecievePackageUpdate::start(uint8* Msg, uint32 Len)
{
  // CSN-4006 - TECH: Improvements to Patch Server
	//if (remoteHostValid())
	//{
	//	BlowfishEncryption enc;
	//	String username;
	//	String password;

	//	enc.decode(Msg+1,Len-1);

	//	if (m_multibyte)
	//	{
	//		int32 offset;
	//		offset = username.deserialize(&Msg[1]);
	//		password.deserialize(&Msg[1] + offset);
	//	}
	//	else
	//	{
	//		char* user = (char*)_alloca(Msg[1] + 1);
	//		char* pswd = (char*)_alloca(Msg[2] + 1);
	//		
	//		user[0] = '\0';
	//		pswd[0] = '\0';
	//		
	//		memcpy(user, &Msg[3],		Msg[1]);
	//		memcpy(pswd, &Msg[3+Msg[1]], Msg[2]);
	//		
	//		user[Msg[1]] = '\0';
	//		pswd[Msg[2]] = '\0';
	//		username = user;
	//		password = pswd;
	//	}
	//	
	//	if (username.getLength() && password.getLength() && 
	//		PatchServer::This->isCorrectLogin(username, password))
	//	{
	//		m_username = username;

	//		Log::msgInformation("User authentication passed: %S.", (const uint16*)username);
	//		
	//		m_tosend = 0;
	//		m_done = false;

	//		return EM_Success;
	//	}
	//	else
	//	{
	//		Log::msgError("User authentication FAILED: %S.", (const uint16*)username);

	//		return EM_BadAuth;
	//	}
	//}
	
	m_done = true;
	return EM_BadRequest;
}



void
RecievePackageUpdate::netIn(int32 Amt)
{
    DEBUGMSG(("RecievePackageUpdate::netIn(%d)", Amt));

	bool8 read = true;
	while(read)
	{	
		if (client->getIUsed() < 1)
		{
			client->readNet();
			return;
		}
		
		if (client->getIUsed() < client->getIBuf()[0])
		{
			client->readNet();
			return;
		}

		uint8 *Msg = client->getIBuf();
		uint32 Len = Msg[0];
        uint32 bytesToEat = Len;

        ASSERT( Len > 0 );

		Msg++;
		Len--;

        if ( *Msg == 's' )
        {
            // The original 'S' protocol only allowed chunks of 253
            // total bytes.  The new lowercase 's' protocol
            // supports larger data blocks
            //
            // Byte     Value
            //   0       4 (header length)
            //   1      's'
            //   2      length (hi-byte)
            //   3      length (low-byte)
            if ( *(Msg - 1) == 4 )
            {
                ++Msg;
                Len = (256 * Msg[0] + Msg[1]);
                bytesToEat = Len;

                ASSERT( Len > 0 );
                Len--;

		        if ((unsigned)client->getIUsed() < bytesToEat)
		        {
                    DEBUGMSG(("RecievePackageUpdate::netIn incomplete message buffer %d of %d", (unsigned)client->getIUsed(), bytesToEat));
			        client->readNet();
			        return;
		        }

                // Now that we have extracted the length pretend we have an 'S' message
                *Msg = 0;
                ++Msg;
                *Msg = 'S';
                Len -= 2; // 'S' protocol is two bytes shorter than 's'
            }
        }

        ASSERT( Len > 0 );

		read = handleMessage(Msg, Len);
		
		client->eatIBuf(bytesToEat);
	}

	client->writeNet();

	return;
}

void
RecievePackageUpdate::netOut(int32 Amt)
{
	client->protocolDone();
}

bool8
RecievePackageUpdate::remoteHostValid()
{
	const int MAXIPLEN = 15;
	char clientip[MAXIPLEN + 1] = { '\0' };

	client->getNet()->getClientIP(clientip, MAXIPLEN + 1);

	if (m_isRemoteHostValid)
	{
		Log::msgInformation("Host authentication passed: %s.", clientip);
	}
	else
	{
		m_isRemoteHostValid = PatchServer::This->isCorrectRemoteUpdateHost(clientip);
		if (m_isRemoteHostValid)
			Log::msgInformation("Host authentication passed: %s.", clientip);
		else
			Log::msgError("Host authentication FAILED: %s.", clientip);
	}


	return m_isRemoteHostValid;
}



bool8
RecievePackageUpdate::unzipPackage(PackageDescription *pkg)
{
	bool8 result = true;

	ZipFile zf;
	PathName unzippeddir;

	unzippeddir.set(pkg->getZipName());
	unzippeddir.shrink(4);

	if(!zf.open(pkg->getZipName()))
		return false;

	if(!doUnzipPackage(zf, unzippeddir))
		result = false;

	zf.close();

	return result;
}

bool8
RecievePackageUpdate::doUnzipPackage(ZipFile &ZF, PathName &Dest)
{
	ZipEntry* ze;
	pCStream out = (pCStream)0;
	UnzipStream* uzs = (UnzipStream*)0;
	int32 amt, origDestLen;
	uint8 buf[4096];

	// caller must clean up Dest directory upon failure
	//
	origDestLen = Dest.getLen();

	ze = ZF.getZipEntries();
	while (ze)
	{
		if (ze->isFilenameValid() && !ze->isEncrypted())
		{
			if (!Dest.concat(ze->filename))
				goto abort;

			// change unix separators (stored in zipfile)
			//
			Dest.replace('/', Directory::sep);

			if (ze->isDirectory())
			{
				if (!Directory::makeAll(Dest))
					goto abort;
			}
			else
			{
				if (!Directory::makeAll(Dest, Directory::EndIsFilename))
					goto abort;

				out = newCStream(Dest, streamO_WRONLY);
				if (!out)
					goto abort;

				uzs = ze->getUnzipStream(&ZF);
				if (uzs)
				{
					while ((amt = uzs->read(buf, 4096)) >= 0)
					{
						if (amt > 0 && amt != CStreamWrite(out, buf, amt))
						{
							amt = -2;
							break;
						}
					}

					if (amt < -1)
						goto abort;

					deleteCStream(out);
					delete uzs;
					out = (pCStream)0;
					uzs = (UnzipStream*)0;
				}
				else
					goto abort;
			}

			Dest.shrink(Dest.getLen() - origDestLen);
		}
		else
			goto abort;

		ze = ze->getNext();
	}

	return true;

abort:
	delete uzs;
	deleteCStream(out);
	Dest.shrink(Dest.getLen() - origDestLen);

	return false;
}

static int iTotal = 0;

bool8
RecievePackageUpdate::handleMessage(uint8* Msg, int32 Len)
{	
	if (Msg[0] == 'U')
	{
		char* name;
		name = new char[Len];
		memcpy(name, &Msg[1], Len-1);
		name[Len-1] = '\0';
		m_zipname = name;
		delete name;

		String path = ServerOpts::patchPackagesPath + m_zipname;

		if (m_stream)
			deleteCStream(m_stream);

		m_stream = newCStream(path, streamO_WRONLY);
		m_multibyte = false;

        iTotal = 0;

		DEBUGMSG(("File %S: OPEN", (const uint16*)m_zipname));
	}
	else if (*((short*)&Msg[0]) == *((short*)"MU"))
	{
		m_zipname.deserialize(&Msg[2]);

		String path = ServerOpts::patchPackagesPath + m_zipname;

		if (m_stream)
			deleteCStream(m_stream);

		m_stream = newCStream(path, streamO_WRONLY);
		m_multibyte = true;

		DEBUGMSG(("%S: M OPEN", (const uint16*)m_zipname));
	}
	else if (Msg[0] == 'S')
	{	
        iTotal += Len - 1;

        DEBUGMSG(("Chunk of %d bytes (%d total)", (Len - 1), iTotal));

		if (m_stream)
		{
			BlowfishEncryption enc;

			enc.decode(&Msg[1],Len-1);
		
			CStreamWrite(m_stream, &Msg[1], Len-1);
		}
	}
	else if (Msg[0] == 'X')
	{
		DEBUGMSG(("File %S: CLOSE", (const uint16*)m_zipname));

		deleteCStream(m_stream);
		m_stream = 0;

		String path = ServerOpts::patchPackagesPath + m_zipname;

        // kharmon.  08-03-06.  Moved common code that was used by both the 
        // network and directory based updates into this "processPackage" method.
        String strPkgName;
        bool8 bSuccess = processPackage(path, m_username, strPkgName);

        if(bSuccess==true)
        {
		    client->msgStart();
		    if (m_multibyte)
			    client->msgAdd((uint8)'M');
		    client->msgAdd((uint8)'R');
		    client->msgAdd((uint8)'S');
		    client->msgAdd(strPkgName, m_multibyte);
		    client->msgEnd();
        }
        else
        {
	        String buffer;
	        if (!strPkgName.getLength())
	        {
		        buffer = m_zipname.substring(0, m_zipname.getLength()-10);
	        }
	        client->msgStart();
	        if (m_multibyte)
		        client->msgAdd((uint8)'M');
	        client->msgAdd((uint8)'R');
	        client->msgAdd((uint8)'F');
	        client->msgAdd(buffer, m_multibyte);
	        client->msgEnd();
        }

		return false;
    }
    else
    {
        DEBUGMSG(("RecievePackageUpdate::handleMessage message of %d bytes skipped: %c%c", Len, Msg[0], Msg[0] == 'M'?Msg[1]:0));
        ASSERT(Len > 0 );
    }

	return true;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  RecievePackageUpdate::processPackage(const String& strPkgName, const String& strUserName)
///
/// This method takes a zipped package file and loads it into the patch server.  Used by both the old network
/// and the new directory based "upload" methods.
///
/// @param  strPkgName          -  (in)  the relative path and package zip file name.  e.g. "pkgs\War v2031.zip"
/// @param  strUserName         -  (in)  name that should be added to event log messages
/// @param  strExtractedPkgName -  (out) The package name that we get out of the manifest, returned to user.
///
/// kharmon.  08-03-06.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool8 RecievePackageUpdate::processPackage(const String& strPkgName, const String& strUserName, String& strExtractedPkgName)
{
    strExtractedPkgName.empty();

    bool8 bRetVal = true;

	PackageDescription* pkg = new PackageDescription();

	String zippath;
	String path = strPkgName;
	//_wfullpath((uint16*)zippath.getBuffer(MAX_PATH+1), (uint16*)(const uint16*)path, MAX_PATH);
	//zippath.releaseBuffer();
	zippath = path;

	pkg->setZipFile(zippath);

	if (!pkg->getName())
	{
		// Log::msgError("User: %S package %S version %d.%d failed because the zip (%s) or the package manifest was invalid.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), zippath);
		// CSN-7534: Use string version
		Log::msgError("User: %S package %S version %S failed because the zip (%s) or the package manifest was invalid.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr(), zippath);
        bRetVal = false;
		goto abort;
	}

    strExtractedPkgName = pkg->getName();   // kharmon.  08-05-06.  #14519.  Return the "official" package name.

	if (!unzipPackage(pkg))
	{
		// Log::msgError("User: %S package %S version %d.%d failed because the zip (%S) could not be unzipped.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)zippath);
		// CSN-7534: Use string version
		Log::msgError("User: %S package %S version %S failed because the zip (%S) could not be unzipped.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr(), (const uint16*)zippath);
        bRetVal = false;
		goto abort;
	}
	
	if (!pkg->set(pkg->getName(), pkg->getVersion(), zippath))
	{
		// Log::msgError("User: %S package %S version %d.%d failed because the zip (%S) could not be loaded.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)zippath);
		// CSN-7534: Use string version
		Log::msgError("User: %S package %S version %S failed because the zip (%S) could not be loaded.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr(), (const uint16*)zippath);
        bRetVal = false;
		goto abort;
	}

	// so far so good.  Let's offically add the new pkg
	PackageList* list = PatchServer::This->lockPackageList();
	if (!list)
	{
		PatchServer::This->unlockPackageList();
        bRetVal = false;
		// Log::msgError("User: %S package %S version %d.%d failed because the package list could not be locked.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL));
		// CSN-7534: Use string version
		Log::msgError("User: %S package %S version %S failed because the package list could not be locked.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr());
		goto abort;
	}

	for(int32 i=0;i<pkg->getNumDependencies();i++)
	{
		PkgDependency* PkgD = &pkg->getDepencies()[i];

		PackageDescription* probe = list->getItem(0);
		while (probe)
		{
			if (0 == probe->getName().caseCompare(PkgD->getName()))
				break;

			probe = probe->getNext();
		}

		if (!probe)
		{
			PatchServer::This->unlockPackageList();
            bRetVal = false;
			// Log::msgError("User: %S package %S version %d.%d failed because required package %S cannot be found.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)PkgD->getName());
			// CSN-7534: Use string version
			Log::msgError("User: %S package %S version %S failed because required package %S cannot be found.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr(), (const uint16*)PkgD->getName());
			goto abort;
		}

		/*
		if (probe->getVersion() < PkgD->getRequiredVersion())		// CSN-5569 - Upgrade any package copied to NewPackages
		{
			PatchServer::This->unlockPackageList();
            bRetVal = false;
			Log::msgError("User: %S package %S version %d.%d failed because required package %S does not meet required version %d.%d.", (const uint16*)strUserName, (const uint16*) pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)PkgD->getName(), (PkgD->getRequiredVersion() >> 16) & 0xFFFFL, (PkgD->getRequiredVersion() & 0xFFFFL));
			goto abort;
		}
		*/
	}

	PackageDescription* probe = list->getItem(0);
	while (probe)
	{
		if (0 == probe->getName().caseCompare(pkg->getName()) ||
			0 == probe->getZipName().caseCompare(pkg->getZipName()))
			break;

		probe = probe->getNext();
	}
		
	if (_waccess(pkg->getZipName(), 0) == -1)
	{
		PatchServer::This->unlockPackageList();
        bRetVal = false;
		// Log::msgError("User: %S package %S version %d.%d failed because zip can not be accessed: %s.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)pkg->getZipName());
		// CSN-7534: Use string version
		Log::msgError("User: %S package %S version %S failed because zip can not be accessed: %s.", (const uint16*)strUserName, (const uint16*)pkg->getName(), pkg->getVersionStr(), (const uint16*)pkg->getZipName());
		goto abort;
	}

	if (probe)
	{
        // kharmon.  08-16-06.  DO NOT install the package if it isn't newer 
        // than the existing version of the package!
        //if(probe->getVersion()<pkg->getVersion())		// CSN-5569 - Upgrade any package copied to NewPackages
		if(probe->getVersion() != pkg->getVersion())		
        {
            // the uploaded package is newer, get rid of the existing package
		    PathName unzippeddir;
		    unzippeddir.set(probe->getZipName());
		    unzippeddir.shrink(4);
		    Directory::removeAll(unzippeddir);

		    CStreamDelFile(probe->getZipName());

		    list->removeItem(probe);

		
		    Log::msgInformation("User: %S upgraded package %S removing old folder %S and zip ", (const uint16*)strUserName, (const uint16*)pkg->getName(), (const uint16*)probe->getZipName());			

        }
        else
        {
            // the existing package is newer than the uploaded version!
            // write an error to the log and get rid of the uploaded files.
		    PatchServer::This->unlockPackageList();

            bRetVal = false;
		    /* 
			Log::msgError("User: %S package %S was not upgraded.  New upload version - %d.%d.  Existing version - %d.%d.  Uploaded version is obsolete.", 
                          (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, 
                          (pkg->getVersion() & 0xFFFFL), (probe->getVersion() >> 16) & 0xFFFFL, (probe->getVersion() & 0xFFFFL));
		    */
			// CSN-7534: Use string version
			Log::msgError("User: %S package %S was not upgraded.  New upload version - %S.  Existing version - %S.  Uploaded version is obsolete.", 
				(const uint16*)strUserName, 
				(const uint16*)pkg->getName(), 
				(const uint16*)pkg->getVersionStr(), 
				(const uint16*)probe->getVersionStr());

		    PathName unzippeddir;
		    unzippeddir.set(pkg->getZipName());
		    unzippeddir.shrink(4);
		    Directory::removeAll(unzippeddir); 

		    CStreamDelFile(pkg->getZipName());

		    goto abort;
        }
	}

	list->addItem(pkg);
	list->save();

	PatchServer::This->unlockPackageList();
	
	if (probe)
	{
		// Log::msgInformation("User: %S upgraded package %S from version %d.%d to %d.%d.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (probe->getVersion() >> 16) & 0xFFFFL, (probe->getVersion() & 0xFFFFL),(pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL) );
		// CSN-7534: Use string version
		Log::msgInformation("User: %S upgraded package %S from version %S to %S", 
			(const uint16*)strUserName, 
			(const uint16*)pkg->getName(), 
			(const uint16*)probe->getVersionStr(),
			(const uint16*)pkg->getVersionStr() );
		
		delete probe;
		probe = 0;
	}
	else
	{
		// Log::msgInformation("User: %S added package %S version %d.%d.", (const uint16*)strUserName, (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL));
		// CSN-7534: Use string version
		Log::msgInformation("User: %S added package %S version %S", (const uint16*)strUserName, (const uint16*)pkg->getName(), pkg->getVersionStr());
	}

abort:

    if(bRetVal==false)
    {
        delete pkg;
        pkg=NULL;
    }

    return bRetVal;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  RecievePackageUpdate::addToDependencySortList(...)
/// 
/// The goal of this method is to take the packages in aFileList and sort them into aSortedList 
/// so that the base packages are first in aSortedList and the packages that depend on them come 
/// later in the list.  The exact order doesn't matter, the key thing is that we always list
/// the packages that are depended on BEFORE we list the packages that depend ON them.
/// We'll load the packages in the order that they appear in aSortedList.
/// Note that this method calls itself recursively.
///
/// @param  currPos    -  (in)     points to the package that we are currently examining in aFileList.
/// @param  aFileList  -  (in/out) This is the list of packages that HAVE NOT BEEN EXAMINED YET.
/// @param  aSortedList-  (in/out) As packages are examined, they are added to this list.  Building
///                                this list in the correct order is the whole point of this method.
/// 
/// @return bool8   -  returns true if all is well, false if an error was encounted in processing 
///                    the package pointed to by currPos.
///
/// kharmon.  08-03-06.
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool8 RecievePackageUpdate::addToDependencySortList(list<String>::iterator& currPos, 
                                                    list<String>& aFileList, 
                                                    list<String>& aSortedList)
{
    String strFile = *currPos;    // get the name of the current package

    // now we want to see if this package is already in the sorted list.
    // if it is, then it's already been processed and we can just return.
    list<String>::iterator sortCurr = aSortedList.begin();
    list<String>::iterator sortEnd = aSortedList.end();
    while(sortCurr != sortEnd)
    {
        String strCurr = *sortCurr;

        if(strCurr == strFile)
        {            
            // this package has already been processed.  Our work is done.
            break;
        }

        ++sortCurr;
    }

    // remove the package from the list, it's either already been processed
    // or it's about to be processed.  Either way, we want it out of "aFileList".
    aFileList.erase(currPos);

    if(sortCurr!=sortEnd)
    {
        // the package has already been processed...we found it in aSortedList.  Return.
        return true; 
    }

    // Okay, we haven't processed this package before.  So, let's unzip it and look at what it depends on.
    bool8 bRetVal = true;

	PackageDescription* pkg = new PackageDescription();

	String zippath;
	String path = ServerOpts::patchNewPackagesPath + strFile;
	//_wfullpath((uint16*)zippath.getBuffer(MAX_PATH+1), (uint16*)(const uint16*)path, MAX_PATH);
	//zippath.releaseBuffer();
	zippath = path;

	pkg->setZipFile(zippath);

	if (!pkg->getName())
	{
        Log::msgError("Verification:  Package %S version %d.%d failed because the zip (%s) or the package manifest was invalid.", (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), zippath);
        bRetVal = false;
		goto abort;
	}

	if (!unzipPackage(pkg))
	{
		// Log::msgError("Verification:  Package %S version %d.%d failed because the zip (%S) could not be unzipped.", (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)zippath);
		// CSN-7534: Use string version
		Log::msgError("Verification:  Package %S version %S failed because the zip (%S) could not be unzipped.", (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr(), (const uint16*)zippath);
        bRetVal = false;
		goto abort;
	}
	
	if (!pkg->set(pkg->getName(), pkg->getVersion(), zippath))
	{
		// Log::msgError("Verification:  Package %S version %d.%d failed because the zip (%S) could not be loaded.", (const uint16*)pkg->getName(), (pkg->getVersion() >> 16) & 0xFFFFL, (pkg->getVersion() & 0xFFFFL), (const uint16*)zippath);
		// CSN-7534: Use string version
		Log::msgError("Verification:  Package %S version %S failed because the zip (%S) could not be loaded.", (const uint16*)pkg->getName(), (const uint16*)pkg->getVersionStr(), (const uint16*)zippath);
        bRetVal = false;
		goto abort;
	}

    int32 nNumDependencies = pkg->getNumDependencies();
    if(nNumDependencies==0)
    {
        // if this package has NO dependencies, add it to the front of
        // the sort list, it's always safe to process these first.
        aSortedList.push_front(strFile);
    }
    else
    {
        // dependencies exist, let's look at each one and see if it's one of the
        // packages in the "NewPackages" directory.  If it is, we need to make sure 
        // that we load it before we load the current package, for dependency reasons.
	    for(int32 i=0;i<nNumDependencies;i++)
	    {
		    PkgDependency* PkgD = &pkg->getDepencies()[i];

            bool8 bFound = false;

	        String strPkgName;

            // here' we're taking the basic package name and adding " v" to it so that
            // we can compare it to the package zip file name.
            strPkgName.sprintf("%S v", (const uint16 *) PkgD->getName());
            int32 nSubStrSize = strPkgName.getLength();

            if(aSortedList.size()>0)
            {
                // first see if this package has already been processed.
                list<String>::iterator findIt = aSortedList.begin();
                list<String>::iterator findEnd = aSortedList.end();
                while(findIt != findEnd)
                {
                    String strCurrPkg = (*findIt).substring(0, nSubStrSize);

                    if(strCurrPkg == strPkgName)
                    {
                        // we've already processed this package, we can skip it.
                        bFound=true;
                        break;
                    }

                    ++findIt;
                }
            }

            if( bFound==false )
            {
                // if we haven't already processed this package, let's see if we
                // are uploading a new version of it.  Look through our list of
                // "unprocessed" package files:
                list<String>::iterator findIt = aFileList.begin();
                list<String>::iterator findEnd = aFileList.end();
                while(findIt != findEnd)
                {
                    String strCurrPkg = (*findIt).substring(0, nSubStrSize);

                    if(strCurrPkg == strPkgName)
                    {
                        // we do have a new version of this package to upload...let's process
                        // it before we continue.
                        bFound=true;
                        break;
                    }

                    ++findIt;
                }

                if(bFound==true)
                {
                    // Since we have a new version of this package to upload...let's process
                    // it before we continue with the current package, so that everything will
                    // be loaded in the correct order.
                    addToDependencySortList(findIt, aFileList, aSortedList);
                }            
            }        
	    }
        
        // OKAY, all packages that the current package depends on should now be in the
        // sort list ahead of us, so we add the current package to the end of the list.
        aSortedList.push_back(strFile);
    }

abort:

    delete pkg;
    pkg=NULL;
    
    // since we unzipped the package to examine the dependencies, let's
    // clean up after ourselves and remove the unzip directory.
    String strRemovePath;   
    int32 nNdx = zippath.reverseFind(".");
    if(nNdx>0)
    {
        strRemovePath = zippath.substring(0, nNdx);
    }
    else
    {
        strRemovePath = zippath;
    }

    Directory::removeAll(strRemovePath);

    return bRetVal;
   
}

////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  RecievePackageUpdate::checkUploadDirectory()
///
/// This method is called to see if there are any new packages that we should upload.
/// Note that the "trigger" file must exist before we will upload any of the packages.
/// Only if a file named "trigger" exists will we look for .zip package files to process.
/// If a trigger file exists, then all zip files will be processed and loaded into the patch
/// server (barring errors of course). 
/// 
/// kharmon.  08-03-06.
////////////////////////////////////////////////////////////////////////////////////////////
void RecievePackageUpdate::checkUploadDirectory()
{
    WIN32_FIND_DATA fileData;

    // check for the existence of the trigger file.
	String trigger = ServerOpts::patchNewPackagesPath + "trigger";
    HANDLE hTriggerFile = FindFirstFileEx(trigger, FindExInfoStandard, 
                                          &fileData, FindExSearchNameMatch, NULL, 0 );

    if(hTriggerFile!=INVALID_HANDLE_VALUE)
    {
        // We found a trigger file!  Let's load the packages!
        FindClose(hTriggerFile);
        
        DeleteFile(trigger);    // delete the trigger...

        // Build a list of all of the package files
		String zips = ServerOpts::patchNewPackagesPath + "*.zip";
        HANDLE hPkgFiles = FindFirstFileEx(zips, FindExInfoStandard, 
                                           &fileData, FindExSearchNameMatch, NULL, 0 );

        list<String> aFileList;
        if(hPkgFiles!=INVALID_HANDLE_VALUE)
        {
            do
            {
                String strFileName = fileData.cFileName;
                aFileList.push_back(strFileName);
            }
            while(FindNextFile(hPkgFiles, &fileData) != 0);

            FindClose(hPkgFiles);
        }

        // now, sort the list of packages into this new list so that we load the base
        // packages before we load the other packages that depend on them.
        list<String> aSortedList;
        
        while(aFileList.size()>0)
        {
            list<String>::iterator it = aFileList.begin();
            RecievePackageUpdate::addToDependencySortList(it, aFileList, aSortedList);
        }        

        if(aSortedList.size()>0)
        {
            // okay, we have our list of sorted packages, lets load them in, one at time
            list<String>::iterator sortIt  = aSortedList.begin();
            list<String>::iterator sortEnd = aSortedList.end();

            while(sortIt != sortEnd)
            {
                // for each package, move it from the "NewPackages" directory to the
                // official "pkgs" directory, which is where it must live to be loaded.
                String strFileName = *sortIt;
				String strSrcFile = ServerOpts::patchNewPackagesPath + strFileName;

                DEBUGMSG(("\n\tFile:  %S", (const uint16 *) strFileName));

				String strTarget = ServerOpts::patchPackagesPath + strFileName;
                
                // move the .zip package file to the "pkgs" directory.
                if(MoveFile(strSrcFile, strTarget))
                {
                    // now load the package
                    String strPkgName;
                    if(RecievePackageUpdate::processPackage(strTarget, L"NewPkgDir", strPkgName)==false)
                    {
                        // Something went wrong when installing this package.  Make a note of it in the log and continue upload.
                        Log::msgError("Could not install package %S.", (const uint16 *) strTarget);
                        DeleteFile(strTarget);
                    }                    
                }
                else
                {
                    // hmmmm...our attempt at moving the .zip file to the "pkgs" directory failed...note it in the log and continue upload.
                    Log::msgError("Could not move package %S from NewPackages to pkgs for install.", (const uint16 *) strSrcFile);                    
					DeleteFile(strSrcFile);
                }

                ++sortIt;   // next package
            }
        }

        DeleteFile(trigger);    // make sure the trigger file was deleted.
    }
}
